﻿using LModBusRTU;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ZLSerialPortBusLib.SerialPortBus;

namespace SY280DRIVER
{
    public enum ReadWriteMask
    {
        OnlyRead=0x01,
        OnlyWrite=0x02,
        ReadWrite=0x03,
    }
    public class RegInfo
    {
        /// <summary>
        /// 寄存器是否可以读写
        /// 0x01：只读；
        /// 0x02：只写；
        /// 0x03：读写；
        /// </summary>
        public int isReadWrite { get; private set; } = 1;
        /// <summary>
        /// 寄存值
        /// </summary>
        private int[] RegValues { get; set; } = new int[0];
        /// <summary>
        /// 寄存器数量
        /// </summary>
        public int RegCount { get { return RegValues.Length; } }
        /// <summary>
        /// 寄存器起始地址
        /// </summary>
        public int AddressBase { get; private set; } = 0;
        /// <summary>
        /// 寄存器值最后刷新时间
        /// </summary>
        public DateTime LastActive { get; private set; } = DateTime.MinValue;
        public bool hasRead { get; set; } = false;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="iAddressBase">寄存器起始地址</param>
        /// <param name="iRegRW">寄存器是否可以读写</param>
        /// <param name="iRegCount">寄存器数量</param>
        public RegInfo(int iAddressBase, ReadWriteMask iRegRW, int iRegCount)
        {
            AddressBase = iAddressBase;
            if (iRegCount > 0)
                RegValues = new int[iRegCount];
            else
                RegValues = new int[1];
            isReadWrite = (int)iRegRW;
        }

        public RegInfo(int iAddressBase, ReadWriteMask iRegRW) : this(iAddressBase, iRegRW, 1)
        {
        }
        /// <summary>
        /// 直接返回AddressBase对应的寄存器数据，也就是说队列偏移0的寄存器值
        /// </summary>
        /// <param name="iDef"></param>
        /// <returns></returns>
        public int getValue()
        {
            int iRegValue = 0;
            getValue(0, out iRegValue);
            return iRegValue;
        }
        /// <summary>
        /// 根据寄存器地址偏移量对应的值
        /// 注意：以寄存器起始地址为基
        /// </summary>
        /// <param name="iDef">地址偏移</param>
        /// <param name="RegValue">返回读取到的值</param>
        /// <returns>读取地址有效，成功读取到数据返回true，否则返回false</returns>
        public bool getValue(int iDef, out int RegValue)
        {
            RegValue = 0;
            if ((RegCount > 0) && (iDef >= 0) && (RegCount > iDef))
            {
                RegValue = RegValues[iDef];
                return true;
            }
            return false;
        }
        /// <summary>
        /// 直接写入AddressBase对应的寄存器数据，也就是说队列偏移0的寄存器值
        /// </summary>
        /// <param name="iValue">等待写入的值</param>
        /// <returns></returns>
        public void setValue(int iValue)
        {
            setValue(iValue, 0);
        }
        /// <summary>
        /// 写入偏移量为iDef的寄存器值
        /// 注意：以寄存器起始地址为基
        /// </summary>
        /// <param name="iValue">等待写入的值</param>
        /// <param name="iDef">地址偏移</param>
        /// <returns>写入地址有效，返回true，否则返回false</returns>
        public bool setValue(int iValue, int iDef)
        {
            if ((RegCount > 0) && (iDef >= 0) && (RegCount > iDef))
            {
                RegValues[iDef] = iValue;
                LastActive = DateTime.Now;
                return true;
            }
            return false;
        }
        /// <summary>
        /// 变更最后刷新时间到指定时间
        /// </summary>
        /// <param name="tDestTime"></param>
        public void resetTime(DateTime tDestTime)
        {
            LastActive = tDestTime;
        }
        
    }
    /// <summary>
    /// SY280变频器的错误代码，参考技术文档
    /// </summary>
    public enum SY280_ErrorCode
    {
        EC_无故障 = 0,
        EC_保留,
        EC_加速过电流,
        EC_减速过电流,
        EC_恒速过电流,
        EC_加速过电压,
        EC_减速过电压,
        EC_恒速过电压,
        EC_缓冲电阻过载故障,
        EC_欠压故障,
        EC_变频器过载,
        EC_电机过载,
        EC_输入缺相,
        EC_输出缺相,
        EC_模块过热,
        EC_外部故障,
        EC_通讯异常,
        EC_接触器异常,
        EC_电流检测故障,
        EC_电机调谐故障,
        EC_编码器PG卡故障,
        EC_参数读写异常,
        EC_变频器硬件故障,
        EC_电机对地短路故障,
        EC_保留1,
        EC_保留2,
        EC_运行时间到达,
        EC_用户自定义故障1,
        EC_用户自定义故障2,
        EC_上电时间到达,
        EC_掉载,
        EC_运行时PID反馈丢失,
        EC_快速限流超时故障,
        EC_运行时切换电机故障,
        EC_速度偏差过大,
        EC_电机超速度,
        EC_电机过温,
        EC_编码器线数设定错误,
        EC_未接编码器,
        EC_初始位置错误,
        EC_速度反馈错误,
    }

    public class SY280RunTime
    {
        /// <summary>
        /// 变频器运行参数信息
        /// </summary>
        public static int RegAddr_RunInfo = 0x1000;
        /// <summary>
        /// 控制命令输入到变频器（只写）
        /// </summary>
        public static int RegAddrW_Control = 0x2000;
        /// <summary>
        /// 读取变频器状态（只读）
        /// </summary>
        public static int RegAddrR_RunStatus = 0x3000;
        /// <summary>
        /// 变频器故障描述
        /// </summary>
        public static int RegAddrR_Error = 0x8000;

        /// <summary>
        /// 基于RegAddr_RunInfo的偏移
        /// </summary>
        public static int RegAddrDef_SetValue = 0x00;           // 设置的运行值
        public static int RegAddrDef_RunFrequency = 0x01;       // 运行频率
        public static int RegAddrDef_InputVoltage = 0x02;       // 母线电压
        public static int RegAddrDef_OutputVoltage = 0x03;      // 输出电压
        public static int RegAddrDef_OutputCurrent = 0x04;      // 输出电流
        public static int RegAddrDef_OutputPower = 0x05;       // 输出功率
        public static int RegAddrDef_OutputMoment = 0x06;       // 输出转矩
        public static int RegAddrDef_OutputSpeed = 0x07;        // 运行速度        

        /// <summary>
        /// 变频器工作状态
        /// 可读可写
        /// </summary>
        public bool IsRunning
        {
            get
            {
                if (iRegister.ContainsKey(RegAddrR_RunStatus))
                {
                    RegInfo tRegInfo = iRegister[RegAddrR_RunStatus];
                    return tRegInfo.getValue() == 1;
                }
                return false;
            }
            set
            {
                if (iRegister.ContainsKey(RegAddrR_RunStatus))
                {
                    RegInfo tRegInfo = iRegister[RegAddrR_RunStatus];
                    if ((tRegInfo.getValue() != 5)&&(!value))
                    {
                        writeOneReg(RegAddrW_Control, 5);
                    }
                    if ((tRegInfo.getValue() != 1) && value)
                    {
                        writeOneReg(RegAddrW_Control, 1);
                    }
                    tRegInfo.resetTime(DateTime.MinValue);
                }
            }
        }

        /// <summary>
        /// 变频器设定运行值：0-1000
        /// 可读可写
        /// </summary>
        public int iRunValue
        {
            get
            {
                if (iRegister.ContainsKey(RegAddr_RunInfo))
                {
                    RegInfo tRegInfo = iRegister[RegAddr_RunInfo];
                    return tRegInfo.getValue();
                }
                return 0;
            }
            set
            {
                if (iRegister.ContainsKey(RegAddr_RunInfo)
                    && (value >= 0) && (value <= 10000))
                {
                    RegInfo tRegInfo = iRegister[RegAddr_RunInfo];
                    writeOneReg(RegAddr_RunInfo, value);
                    tRegInfo.resetTime(DateTime.MinValue);
                }
            }
        }

        /// <summary>
        /// 变频器故障代码
        /// 只读
        /// </summary>
        public SY280_ErrorCode Error
        {
            get
            {
                if (iRegister.ContainsKey(RegAddrR_Error))
                {
                    RegInfo tRegInfo = iRegister[RegAddrR_Error];
                    return (SY280_ErrorCode)tRegInfo.getValue();
                }
                return 0;
            }
        }

        /// <summary>
        /// 变频器是否在线
        /// </summary>
        public bool IsOnline { get; private set; } = false;

        protected Dictionary<int, RegInfo> iRegister = new Dictionary<int, RegInfo>();

        /// <summary>
        /// 当前等待应答的命令
        /// </summary>
        protected TModbusRTUCommandV2 tMBCommandRunning = null;
        /// <summary>
        /// 当前等待应答的命令开始时间，用于命令超时
        /// </summary>
        protected DateTime TMBCommandStar { get; set; } = DateTime.Now;

        /// <summary>
        /// 串口通讯控件，设置并启动该串口，将导致本控制自动开始更新寄存器数据
        /// </summary>
        public ZLSerialPortBase tSerialPort { get;private set; } = null;

        public SY280RunTime()
        {
            // 变频器运行状态寄存器，读写
            iRegister.Add(RegAddr_RunInfo, new RegInfo(RegAddr_RunInfo, ReadWriteMask.ReadWrite, 8));
            // 变频器控制寄存器，只写
            iRegister.Add(RegAddrW_Control, new RegInfo(RegAddrW_Control, ReadWriteMask.OnlyWrite));
            // 变频器控制状态，只读
            iRegister.Add(RegAddrR_RunStatus, new RegInfo(RegAddrR_RunStatus, ReadWriteMask.OnlyRead));
            // 变频器错误代码，只读
            iRegister.Add(RegAddrR_Error, new RegInfo(RegAddrR_Error, ReadWriteMask.OnlyRead));
            
            Thread thdMRun = new Thread(ThdRun);
            thdMRun.Start();
        }

        /// <summary>
        /// 设置串口控件
        /// 成功设置打开的串口控件，将启动本对象的所有功能，即开始读取SY280的寄存器，并能够进行控制
        /// </summary>
        /// <param name="tDestSerialPort"></param>
        /// <returns></returns>
        public bool setSerialPort(ZLSerialPortBase tDestSerialPort)
        {
            tSerialPort = tDestSerialPort;
            if (tSerialPort != null)
                tSerialPort.OnRecResponDatas += TSerialPort_OnRecResponDatas;
            return (tSerialPort != null) && tSerialPort.IsOpen;
        }

        public void CloseAll()
        {
            // 关闭线程
            ThdActive = false;
            // 关闭串口并清场，该控件不负责关闭串口
            tSerialPort = null;
        }

        /// <summary>
        /// 串口控件负责CRC校验，成功校验到CRC后，调用该函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="fRecDatas"></param>
        private void TSerialPort_OnRecResponDatas(object sender, byte[] fRecDatas)
        {
            byte[] tDatas = new byte[fRecDatas.Length];
            for (int i = 0; i < fRecDatas.Length; i++)
                tDatas[i] = fRecDatas[i];
            if (tMBCommandRunning!=null)
            {
                try
                {
                    // 发现有等待处理的命令，则检验是否是
                    tMBCommandRunning.CheckReply(tDatas);
                }
                finally
                {
                    tMBCommandRunning = null;
                }
                IsOnline = true;
            }
        }

        private bool ThdActive = true;

        private void ThdRun()
        {
            while (ThdActive)
                try
                {
                    if ((tSerialPort != null) && tSerialPort.IsOpen
                        && (tMBCommandRunning == null))
                    {
                        bool isComplete = true;
                        foreach (KeyValuePair<int, RegInfo> item in iRegister)
                            // 判断是否需要读寄存器
                            if ((item.Value.isReadWrite & (int)ReadWriteMask.OnlyRead) != 0)
                            {
                                if (!item.Value.hasRead)
                                {
                                    isComplete = false;
                                    TimeSpan tSendPas = DateTime.Now - item.Value.LastActive;
                                    
                                    // 300毫秒间隔
                                    if (tSendPas.TotalMilliseconds > 300)
                                    {
                                        tMBCommandRunning = new TModbusRTUCommandV2();
                                        tMBCommandRunning.RegReadAddress = (ushort)item.Key;
                                        tMBCommandRunning.RegReadCount = (ushort)item.Value.RegCount;
                                        tMBCommandRunning.PLCAddress = 1;
                                        tMBCommandRunning.OnGetRTURespon += TMBCommandRunning_OnGetRTURespon;
                                        TMBCommandStar = DateTime.Now;
                                        tSerialPort.SendData(tMBCommandRunning.GetPacketDataNoCRC());

                                        item.Value.hasRead = true;
                                        break;
                                    }
                                    else
                                    {
                                        break;
                                    }
                                }
                            }
                        if (isComplete)
                            foreach (KeyValuePair<int, RegInfo> item in iRegister)
                                // 判断是否需要读寄存器
                                if ((item.Value.isReadWrite & (int)ReadWriteMask.OnlyRead) != 0)
                                    item.Value.hasRead = false;
                    }
                    // 检查命令超时
                    if (tMBCommandRunning != null)
                    {
                        TimeSpan tSendPas = DateTime.Now - TMBCommandStar;
                        // 1000毫秒超时
                        if (tSendPas.TotalMilliseconds > 1000)
                        {
                            try
                            {
                                // 标记命令应答超时
                                tMBCommandRunning.CheckReply(null);
                                IsOnline = false;
                            }
                            finally
                            {
                                // 清理待处理命令
                                tMBCommandRunning = null;
                            }
                        }
                    }
                }
                finally
                {
                    Thread.Sleep(1);
                }
        }
        /// <summary>
        /// Modbus命令收到应答后的调用
        /// 应答可能是正确应答，也可能为空，为空表示命令超时，可能是SY280不在线
        /// </summary>
        /// <param name="sender"></param>
        private void TMBCommandRunning_OnGetRTURespon(object sender)
        {
            TModbusRTUCommandV2 tMBCommandTmp = (TModbusRTUCommandV2)sender;
            if (tMBCommandTmp.HasReply && iRegister.ContainsKey(tMBCommandTmp.RegReadAddress))
            {
                RegInfo tRegInfo = iRegister[tMBCommandTmp.RegReadAddress];
                // 强制刷新数据更新时间
                tRegInfo.setValue(0, 0);
                if ((tMBCommandTmp.Command == ModbusCommand.ReadMemoryReg_0x03))
                {
                    // 读取寄存器命令
                    if (tRegInfo.RegCount == tMBCommandTmp.RegReadCount)
                    {
                        ushort[] tRegReplayValues = tMBCommandTmp.ReplyValues;
                        if (tRegReplayValues.Length == tRegInfo.RegCount)
                            for (int i = 0; i < tMBCommandTmp.RegReadCount; i++)
                                tRegInfo.setValue(tRegReplayValues[i], i);
                    }
                }
                if ((tMBCommandTmp.Command == ModbusCommand.WriteOneMemReg_0x06))
                {
                    // 写入命令
                    ushort[] tRegReplayValues = tMBCommandTmp.RegValue;
                    tRegInfo.setValue(tRegReplayValues[0], 0);
                }
            }
        }

        /// <summary>
        /// 向SY280写入1个寄存器数据
        /// </summary>
        /// <param name="iRegAddress"></param>
        /// <param name="iValue"></param>
        /// <returns></returns>
        public bool writeOneReg(int iRegAddress,int iValue)
        {
            bool iResult = false;
            if ((tSerialPort != null) && tSerialPort.IsOpen
                && iRegister.ContainsKey(iRegAddress)
                // 判断寄存器是否可以写入
                && ((iRegister[iRegAddress].isReadWrite & (int)ReadWriteMask.OnlyWrite) != 0))
            {
                RegInfo tRegInfo = iRegister[iRegAddress];
                // 判断是否有命令正在等待
                while (tMBCommandRunning != null)
                    // 有命令等待，等待命令执行完毕
                    Thread.Sleep(1);
                if (tMBCommandRunning == null)
                {
                    // 创建写入寄存器命令
                    tMBCommandRunning = new TModbusRTUCommandV2(ModbusCommand.WriteOneMemReg_0x06);
                    tMBCommandRunning.PLCAddress = 1;
                    tMBCommandRunning.RegWriteAddress = (ushort)iRegAddress;
                    tMBCommandRunning.SetWriteRegValues(new ushort[] { (ushort)iValue });
                    tMBCommandRunning.OnGetRTURespon += TMBCommandRunning_OnGetRTURespon;
                    tSerialPort.SendData(tMBCommandRunning.GetPacketDataNoCRC());
                    iResult = true;
                }
            }
            return iResult;
        }
    }
}
